// On my honor:
//
// - I have not used source code obtained from another student,
// or any other unauthorized source, either modified or
// unmodified.
//
// - All source code and documentation used in my program is
// either my original work, or was derived by me from the
// source code published in the textbook for this course.
//
// - I have not discussed coding details about this project with
// anyone other than my partner (in the case of a joint
// submission), instructor, ACM/UPE tutors or the TAs assigned
// to this course. I understand that I may discuss the concepts
// of this program with other students, and that another student
// may help me debug my program so long as neither of us writes
// anything during the discussion or modifies any computer file
// during the discussion. I have violated neither the spirit nor
// letter of this restriction.

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.StringTokenizer;

/**
 * @author Christopher Suh, Eric Yoo
 * 
 */
public class Bindisk {
	static BTree bst;
	static PRQuadFile qt;
	static BufferPool pool;
	static MemManager manager;
	
	public static void main(String[] args) {
		// check argument count
		if (args.length != 3) {
			System.err.println("Run Bindisk with the proper arguments.");
			System.err
					.println("\tBindisk <command-file> <num-buffers> <block-size>");
			System.err.println("command-file\ttext file with commands");
			System.err.println("num-buffers\tnumber of buffers in buffer pool");
			System.err.println("block-size\tnumber of bytes in a buffer block");
		} else {
			if (!parseArguments(args)) {
				System.err.println("The program encountered an error while");
				System.err.println("attempting to process the file.");
			}
		}
	}

	/**
	 * parse the arguments passed to the program
	 */
	public static boolean parseArguments(String[] args) {
		File inFile = null;
		BufferedReader br = null;
		String line = "";
		String fileName = args[0];
		
		// attempt to open the file
		inFile = new File(fileName);
		try {
			br = new BufferedReader(new InputStreamReader(new FileInputStream(
					inFile)));
		} catch (FileNotFoundException ex) {
			System.err.printf("Command file %s does not exist!\n", args[0]);
			return false;
		}

		try {
			pool = new BufferPool(Integer.valueOf(args[1]).intValue(), Integer
					.valueOf(args[2]).intValue(), "p4bin.dat");
			pool.clearFile();
		} catch (FileNotFoundException ex) {
			System.err
					.println("Error while opening/creating file 'p4bin.dat' for output.");
			return false;
		} catch (IOException e) {
			System.err.println("IOException while attempting to truncate file.");
		}

		manager = new MemManager(Integer
				.valueOf(args[2]).intValue(), pool);
		bst = new BTree(new StringComparator(), pool);
		qt = new PRQuadFile(0, 16384, 0, 16384, manager, pool);

		// iterate through file line by line
		try {
			line = br.readLine();
		} catch (IOException ex) {
			System.err.println("IOException error");
			return false;
		}

		while (line != null) {
			System.out.println("********************************");
			StringTokenizer tokenizer = new StringTokenizer(line);

			// parse the lines by string tokens and process commands
			if (tokenizer.hasMoreTokens()) {
				String command = tokenizer.nextToken();

				// takes "insert x y city_name" with any whitespace format
				if (command.equals("insert")) {
					// attempt to parse x and y values from file
					int x, y;
					try {
						x = (Integer.parseInt(tokenizer.nextToken()));
						y = (Integer.parseInt(tokenizer.nextToken()));
					} catch (NumberFormatException exc) {
						System.err.println("insert command called with x, y");
						System.err.println("coordinates in wrong format.");
						return false;
					}
					String cityName = tokenizer.nextToken();

					pool.flush();
					insert(x, y, cityName);
				}

				// takes "remove x y" or "remove cityName" with any whitespace
				// format
				else if (command.equals("remove")) {
					if (tokenizer.countTokens() == 2) {
						// attempt to parse the coordinates of city to be
						// removed
						int x, y = 0;
						try {
							x = (Integer.parseInt(tokenizer.nextToken()));
							y = (Integer.parseInt(tokenizer.nextToken()));
						} catch (NumberFormatException exc) {
							System.err
									.println("remove command called with x, y");
							System.err.println("coordinates in wrong format.");
							return false;
						}
						remove(x, y);
					} else if (tokenizer.countTokens() == 1) {
						String cityName = tokenizer.nextToken();
						remove(cityName);
					} else {
						System.err
								.println("remove command called with wrong format");
						return false;
					}
				}

				// takes "find cityName" command with any whitespace format
				else if (command.equals("find")) {
					if (tokenizer.countTokens() == 1) {
						String cityName = tokenizer.nextToken();
						find(cityName);
					} else {
						System.err
								.println("find command called with wrong format");
						return false;
					}
				} else if (command.equals("search")) {
					if (tokenizer.countTokens() == 3) {
						// attempt to parse the coordinates and radius to be
						// searched
						int x, y, radius = 0;
						try {
							x = (Integer.parseInt(tokenizer.nextToken()));
							y = (Integer.parseInt(tokenizer.nextToken()));
							radius = (Integer.parseInt(tokenizer.nextToken()));
						} catch (NumberFormatException exc) {
							System.err
									.println("remove command called with x, y, radius");
							System.err.println("coordinates in wrong format.");
							return false;
						}
						search(x, y, radius);
					} else {
						System.err
								.println("search command called with wrong format");
						return false;
					}
				} else if (command.equals("makenull")) {
					makenull();
				} else if (command.equals("debug")) {
					debug();
				} else {
					System.err.println("Unknown command or wrong file format.");
					System.err
							.println("Please check the " + fileName + " file");
					return false;
				}
			}

			try {
				line = br.readLine();
			} catch (IOException ex) {
				System.err.println("IOException error");
				return false;
			}
			
			System.out.println("********************************\n");
		}

		try {
			br.close();
		} catch (IOException ex) {
			System.err.println("IOException error while closing command file.");
		}
		pool.flush();
		return true;
	}
	
	public static boolean insert(int x, int y, String name) {
		if (x < 0 || x >= 16384) {
			System.out.println("Insert rejected, bad X coordinate");
			System.out.flush();
			return false;
		} else if (y < 0 || y >= 16384) {
			System.out.println("Insert rejected, bad Y coordinate");
			System.out.flush();
			return false;
		} else {
			Coordinates coord = new Coordinates(x, y);
			Integer handle = manager.allocate(name.length());
			
			if (name.equals("E")) {
				System.out.println("Inserting E at " + handle);
			}
			
			try {
				pool.write(handle, name.getBytes(), (byte)name.length());
			} catch (IOException e) {
				System.err.println("IOException while writing city name to file.");
			}
			City c = new City(handle, coord);
			// attempt to insert to quadtree
			if (qt.insert(c)) {
				// insert to BST if insert to quadtree was successful
				bst.insert(name, handle, c);
				System.out.println("Insert operation successful: " + c + "," + name);
				System.out.flush();
				return true;
			} else {
				// insert failed, deallocate city name from file
				manager.deallocate(handle);
				System.out
						.println("Insert rejected, coordinates duplicate an existing city record");
				System.out.flush();
				return false;
			}
		}
	}

	public static City remove(int x, int y) {
		if (x < 0 || x >= 16384) {
			System.out.println("Remove rejected, bad X coordinate");
			System.out.flush();
			return null;
		} else if (y < 0 || y >= 16384) {
			System.out.println("Remove rejected, bad Y coordinate");
			System.out.flush();
			return null;
		} else {
			// attempt to remove from quadtree to make sure the city exists in
			// the database
			Coordinates coord = new Coordinates(x, y);
			City temp = qt.remove(coord);
			if (temp != null) {
				// remove from BST if first remove was successful
				bst.remove(readString(temp.handle()), temp);
				System.out.println("Removed: " + temp + "," + readString(temp.handle()));
				// deallocate city name from file (QT-oriented)
				manager.deallocate(temp.handle());
				System.out.flush();
			} else {
				System.out.println("No such City record found");
				System.out.flush();
			}
			return temp;
		}
	}

	public static City remove(String name) {
		// first, remove from bst to make sure the city exists in the
		// database
		if (name.equals(new String("E"))) System.out.println("Here is E!**");
		City temp = bst.remove(name);
		if (temp != null) {
			// remove from quad tree if non null value is returned
			qt.remove(temp.getCoordinates());
			System.out.println("Removed: " + temp + "," + readString(temp.handle()));
			// deallocate (BST-oriented) string from file
			manager.deallocate(temp.handle());
			System.out.flush();
		} else {
			System.out.println("No such City record found");
			System.out.flush();
		}
		return temp;
	}

	public static void find(String name) {
		ArrayList<City> list = bst.findAll(name);
		System.out.println("City Records found:");
		System.out.flush();
		if (list == null) {
			System.out.println("No records");
			System.out.flush();
		} else
			for (City c : list) {
				System.out.print(c + ",");
				System.out.println(readString(c.handle()));
				System.out.flush();
			}
		System.out.flush();
	}

	public static void search(int x, int y, int radius) {
		Coordinates coord = new Coordinates(x, y);
		if(x < -16384 || x >= 16384)
		{
			System.out.println("Search operation failed: Bad X coordinate");
			System.out.flush();
		}
		else if(y < -16384 || y >= 16384)
		{
			System.out.println("Search operation failed: Bad Y coordinate");
			System.out.flush();
		}
		else if(radius < 0)
		{
			System.out.println("Search operation failed: Bad Radius value");
			System.out.flush();
		}
		else
		{
			System.out.println("City Record(s) found:");
			int trav = qt.search(coord.getXCoord(), coord.getYCoord(), radius);
			System.out.println("" + trav + " nodes visited");
		}
	}

	public static void debug() {
		System.out.println("Debug: ");
		pool.printBlockIDs();
		manager.dump();
		qt.debug();
	}

	public static void makenull() {
		/* implement manager and pool clears */
		manager.clear();
		try {
			pool.clear();
		} catch (IOException e) {
			e.printStackTrace();
		}
		bst = new BTree(new StringComparator(), pool);
		qt = new PRQuadFile(0, 16384, 0, 16384, manager, pool);
		System.out.println("Makenull operation successful");
		System.out.flush();
	}
	
	static String readString(Integer handle) {
		byte[] buff = new byte[256];
		int size = 0;
		try {
			size = pool.read(handle, buff);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return new String(buff, 0, size);
	}
}
